import * as _ from 'lodash';
import * as fs from 'fs-extra';
import { Zipper } from '$domain/service';
import { Storage } from '$infra';
import { reportService } from '../service/reportService';
import { BaseSimulationJobState } from './baseJobState';
import { JobStatus, JobResultNotification } from '../type';
import { DriverLaunchParameter, DriverResult } from '$domain/model/driver';
import { generateRandomZipPath, JsonFileUtility } from '$common';
import { agentConfig } from '$domain/model/agent/config';

export class FinishingState extends BaseSimulationJobState {

  constructor() {
    super('Post-Running State');
  }

  private async uploadSimulationResult(simulationResultDir: string): Promise<string | null> {
    if (!simulationResultDir) {
      return null;
    }

    const { zipPath: tmpLocalZipPath, randomDir } = generateRandomZipPath();
    this.logger.debug(`📦 zipping directory(jobId${this.jobId}): ${simulationResultDir} to ${tmpLocalZipPath}`);

    await new Zipper({
      info: this.logger.info,
      debug: this.logger.debug,
      warn: this.logger.warn,
      error: this.logger.error
    }).zipDirectory(simulationResultDir, tmpLocalZipPath);

    const simResultZipPath = `${this.storagePath.getSimResultPath(this.jobId)}/sim-result.zip`;

    this.logger.info(`⏫ uploading(jobId=${this.jobId}) ${tmpLocalZipPath} to ${simResultZipPath}`);
    await Storage.Instance.fPutObject(simResultZipPath, tmpLocalZipPath);

    this.logger.debug(`🆑 delete tmp random directory(${this.jobId}): ${randomDir}`);
    fs.removeSync(randomDir);

    return simResultZipPath;
  }

  private _cleanup() {
    try {
      const simInputFilePath = this.simulationPath.getSimInputFile();
      if (fs.pathExistsSync(simInputFilePath)) {
        const d = JsonFileUtility.deserializeFromFile<DriverLaunchParameter>(simInputFilePath);
        const modelDir = d.model.file;
        const modelType = d.model.type;
        if (modelType === 'remote' && modelDir && fs.pathExistsSync(modelDir)) {
          this.logger.info(`🆑 clean up model dir: ${modelDir}`);
          fs.removeSync(modelDir);
        }
      }
    } catch (e) {
      this.logger.warn(`clean up model dir error ${e}`);
    }
    
  }

  async doRun() {
    const jobStatus = this.simJobRepo.getJobStatus(this.jobId);
    if (jobStatus !== JobStatus.Runing) {
      this.logger.warn(`only running state can transition to finished, job(jobId = ${this.jobId} status is ${jobStatus})`);
      if (agentConfig.modelCleanup && agentConfig.failedJobCleanup) {
        this._cleanup();
      }
      return;
    }

    const driverOutputFile = this.simulationPath.getSimOutputFile();

    if (!fs.pathExistsSync(driverOutputFile)) {
      const simJob = this.simJobRepo.getById(this.jobId);
      if (simJob) {
        throw new Error(`job(jobId = ${this.jobId}) exited, exit message: ${simJob.exitMessage}`);
      } else {
        throw new Error(`driverOutputFile: ${driverOutputFile} is not exist.`);
      }
    }

    const driverResult = JsonFileUtility.deserializeFromFile<DriverResult>(driverOutputFile);

    if (!driverResult) {
      throw new Error('Driver result file not generated');
    }

    this.logger.debug(`driverResult(jobId=${this.jobId}): ${JSON.stringify(driverResult, null, 2)}`);

    const jobResult: JobResultNotification = {
      id: this.jobId,
      success: driverResult.success,
      message: driverResult.message,
      result: {}
    };
    if (driverResult.success) {
      const simulationResultZipPath = await this.uploadSimulationResult(driverResult.data.simulationResultDir);
      const mergedOutputParameters = await this.simParameterService.transformOutputParameters(driverResult.data.specifiedParameters);
      jobResult.result = {
        simulationResultZip: simulationResultZipPath,
        specifiedParameters: mergedOutputParameters
      };
    }

    this.simJobRepo.updateBySelective(this.jobId, { result: jobResult });
    this.simJobRepo.updateJobStatus(this.jobId, JobStatus.Finished);
    await reportService.jobResultReport(jobResult);

    if (agentConfig.modelCleanup) {
      if (!driverResult.success && !agentConfig.failedJobCleanup) {
        this.logger.debug(`job(jobId=${this.jobId}) failed, model clean up is false`);
      } else {
        this._cleanup();
      }
    }
  }
}